// include the basic windows header files and the Direct3D header file

#include <windows.h>
#include <windowsx.h>
#include <d3dx9.h>
#include <d3d9.h>
#include <conio.h>

#include "GameCheck.h"
#include "LevelTest.h"
#include "Texture.h"
#include "Camera.h"
#include "YarnBall.h"
#include "ControlScheme.h"
#include "Animation.h"

////////////////////
//GLOBAL VARIABLES//
////////////////////
const double FPS_LOCK = 1.0f/60.0f; // In 1 second render 60 frames -> second/frame
int nFPS=0;						    // Number of Frames Per Second
int nFramesRendered=0;				// FPS without lock

XboxControllerScheme_1 * player_1; // Instance of xbox control scheme 1
KeyboardScheme_1 keyboard;		   // Instance of keyboard control scheme 1

Camera camera;					   // Camera
Text * myText;					   // Basic Text
Texture * yarnTexture;			   // Yarnballs Texture
YarnBall * mainCharacter;		   // An instance of the mainCharacter

AnimationSheet * sheet;			   //For Testing Animation
Texture * aTexture;				   //For Testing Animation(The Animation Texture Sheet)
Sprite * sprite;				   //For Testing Animation

//For camera x y zoom (Not really being used)
float mx = 0.0f;   // Camera X Position
float my = 0.0f;   // Camera Y Position	
float mz = 0.0f;   // Camera Zoom

int textsize = 70; // Size of Text

///////
//END//
///////

// define the screen resolution
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600


// global declarations
LPDIRECT3D9 d3d;			 // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LevelTest * leveltest;       // The pointer to our test level

// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void Update(double dt);		// updates all the tranformation, animation and other data
void render_start(void);
void render_scene(void);    // renders a single frame
void render_end(void);
void cleanD3D(void);        // closes Direct3D and releases memory
void init_graphics(void);   // 3D declarations
void GameStats(double dt);	// Show Stats

// the WindowProc function prototype (Catches Keyboard and Mouse Events)
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
	AllocConsole();	//Creates a console

    HWND hWnd;		//Window Instance
    WNDCLASSEX wc;	//Window Description

    ZeroMemory(&wc, sizeof(WNDCLASSEX));	  // Zero out all the memory instead of having to initialize every parameter

    wc.cbSize = sizeof(WNDCLASSEX);			  // Set the size of the description (I don't know why maybe to make sure the data is consistant)
    wc.style = CS_HREDRAW | CS_VREDRAW;		  // Horizontal and Verticle Redraw
    wc.lpfnWndProc = WindowProc;			  // The windows key event handling function
    wc.hInstance = hInstance;				  // Program Handle
    wc.hCursor = LoadCursor(NULL, IDC_ARROW); //Type of cursor
    wc.lpszClassName = "WindowClass";		  //Name of this description

    RegisterClassEx(&wc);

	// Create a Window
    hWnd = CreateWindowEx(NULL,							// The Style of the window
                          "WindowClass",				// Name of the window
                          "String Game",				// Window Title
                          WS_OVERLAPPEDWINDOW,			// Other Style 
                          0, 0,							// Window X and Y Position
                          SCREEN_WIDTH, SCREEN_HEIGHT,  // Window Width and Height
                          NULL,							// Window Parent
                          NULL,							// Window Menu
                          hInstance,					// Program Handle
                          NULL);						// Not too sure but possibly something about keyboard focus

    ShowWindow(hWnd, nCmdShow);							// Make the window visible 

	///////////////////
	///////////////////
	///////////////////

    // set up and initialize Direct3D
    initD3D(hWnd);
	
	////////////////////////////
	// Test Code For Animation//
	////////////////////////////
	sheet = new AnimationSheet();	//Allocate memory space for animation
	//sheet->SetFPSRate(5);			//defaults at 12 if not set

	sprite = new Sprite();				//Allocate space for sprite
	sprite->init(d3ddev);				//Initialize sprite
	sheet->AnimationForEntity(sprite);	//Tell animation sheet to which object it will be affecting
	aTexture = new Texture(d3ddev);		//Allocate space for texture

	//Load texture and check to see if it fails to load
	if(!aTexture->LoadTexture("../../Art/Animation/TestAnimation.tga", -2))
		_cprintf("Failed to load animation texture!\n");
	sheet->SetTextureSheet(aTexture, 256);	//Set texture and frame dimension
	///////
	///////

	//(New Code) Setup Controls
	player_1 = new XboxControllerScheme_1(1); // Create a new instance and make the first player use control scheme 1	(player_1 is a global variable ptr)
	player_1->SetCharacter(mainCharacter);	  // Pass mainCharacter ptr to be controlled by player						(player_1 is a global variable ptr)
	player_1->SetCamera(&camera);			  // Pass camera instance address to be controlled by player				(player_1 is a global variable ptr)
	keyboard.SetCharacter(mainCharacter);     // Pass mainCharacter ptr to be controlled by player  using keyboard		(keyboard is a global variable)
	keyboard.SetCamera(&camera);			  // Pass camera instance address to be controlled by player using keyboard (keyboard is a global variable)

    // Enter the main loop:
    MSG msg;
	msg.message=0;

	// Counter
	__int64 cntsPerSec = 0;
	QueryPerformanceFrequency((LARGE_INTEGER*)&cntsPerSec);
	float secsPerCnt = 1.0f/(float)cntsPerSec;

	__int64 prevTimeStamp = 0;
	QueryPerformanceCounter((LARGE_INTEGER*)&prevTimeStamp);


	// Game Loop
	while(msg.message != WM_QUIT)
	{

		//Check For A Key Press
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
		else
		{
			__int64 currTimeStep = 0;
			QueryPerformanceCounter((LARGE_INTEGER*)&currTimeStep);
			double dt = (currTimeStep - prevTimeStamp)*secsPerCnt;

			static double ElapseTime=0.0f;
			static double ElapsedUpdateTime=0.0f;
			
			ElapsedUpdateTime += dt;
			ElapseTime += dt;
			nFramesRendered++;

			//////////////////////////////////////////////////////////////
			// Render (Keeping Max FPS At 60FPS No Matter the computer) //
			//////////////////////////////////////////////////////////////
			Update(dt); //Update

			if(ElapsedUpdateTime >= FPS_LOCK)	//Render Scene
			{
				render_start();				//Tell Directx that it can draw to screen
					render_scene();				//Draw scene
					ElapsedUpdateTime = 0.0;	//Reset frame render time
					nFPS++;						//How many frames are being render
			}

					GameStats(dt);					//Update stats (updates every second) : This function causes a major decrease in performance because of text -> need to manage the text better

			render_end();					//Stop Drawing to screen and switch out the draw buffer

			////
			////

			if(ElapseTime>=1.0)
			{
				nFramesRendered=0;	// Total amount of frames procesable without FPS_Lock
				ElapseTime=0.0;		// Reset this conditions timer
			}

			prevTimeStamp = currTimeStep;
		}
    }

    // Clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	//Control mainCharacter using the keyboard and mouse (only have active keyboard keys atm)
	keyboard.StartConrolling(hWnd, message, wParam, lParam);

    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0); // Calls WM_QUIT basically exiting the program
                return 0;
            } break;

		case WM_CHAR: //Check to see if a letter key is pressed

			if('A' == wParam || 'a' == wParam)
			{
				textsize++;
				myText->setPointSize(textsize); // Increasing the Text Size
			}

			if('Z' == wParam || 'z' == wParam)
			{
				textsize--;
				myText->setPointSize(textsize); // Decreasing the Text Size
			}

			if('R' == wParam || 'r' == wParam)
			{
				//Nothing
			}

			if('L' == wParam || 'l' == wParam)
			{
				//Nothing
			}

			if('K' == wParam || 'k' == wParam)
			{
				//Nothing
			}

			if('H' == wParam || 'h' == wParam)
			{
				//Nothing
			}

			if('P' == wParam || 'p' == wParam)
			{
				//Nothing
			}


		break;

		case WM_KEYDOWN: //Check to see if a key is down

			if(VK_ESCAPE == wParam)
				PostQuitMessage(0);

			if(VK_UP == wParam)
			{
				//Nothing
			}
			else if(VK_DOWN == wParam)
			{
				//Nothing
			}

			if(VK_LEFT == wParam)
			{
				//Nothing
			}
			else if(VK_RIGHT == wParam)
			{
				//Nothing
			}

			if (VK_SPACE == wParam)
			{ 
				//Nothing
			}

			if(VK_SHIFT == wParam)
			{
				//Camera zoom in
				mz += 1.0f;	
				camera.Zoom(mz);
			}
			
			if(VK_RETURN == wParam)
			{
				//Camera zoom out
				mz -= 1.0f;
				camera.Zoom(mz);
			}

			if(VK_F12 == wParam)
			{
				// Use for snapshots and save them out (doesn't work atm)
				//D3DXSaveSurfaceToFileA("../../SnapShot/Test.jpg", D3DXIFF_JPG, 0/*Surface*/, 0/*Palette*/, 0/*Rect*/);
			}
			

			break;

		case WM_KEYUP:
			break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION); //Tell Directx which version of directx we are using

	// Set a default display mode for the time being
    D3DDISPLAYMODE d3ddm;
    d3d->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm ); // Create a default display mode

	// Set the directx parameters
    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = TRUE;						// Use a window instead of full screen
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;	// Draw to the back buffer. Discard the front buffer and move the back buffer to the front
    d3dpp.hDeviceWindow = hWnd;					// The Intance of the window we use
	d3dpp.BackBufferCount=1;					// how many back buffers
	d3dpp.BackBufferFormat = d3ddm.Format;		// default buffer format
    d3dpp.BackBufferWidth = SCREEN_WIDTH;		// DrawBuffer Width
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;		// DrawBuffer Height
	d3dpp.EnableAutoDepthStencil = TRUE;		// Use Depth and Stencil buffers
	d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;// Number of bits for the depth and stencil buffer
	d3dpp.PresentationInterval   = D3DPRESENT_INTERVAL_IMMEDIATE; // Switch out the buffers as fast as possible

    // create a device class using this information and the info from the d3dpp stuct

	// Check to see if hardware vertex processing is available if not fall back to software processing
	if(FAILED(d3d->CreateDevice(D3DADAPTER_DEFAULT,
								D3DDEVTYPE_HAL,
								hWnd,
								D3DCREATE_HARDWARE_VERTEXPROCESSING,
								&d3dpp,
								&d3ddev)))

	{
		d3d->CreateDevice(D3DADAPTER_DEFAULT,
						  D3DDEVTYPE_HAL,
						  hWnd,
						  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
						  &d3dpp,
						  &d3ddev);
		_cprintf("D3DCREATE_HARDWARE_VERTEXPROCESSING Failed!\nUsing D3DCREATE_SOFTWARE_VERTEXPROCESSING Instead!\n");
	}

	// Checking shader model version
	if( CheckShaderModel20(d3ddev) )
		_cprintf("Shader Model 2.0 Support!\n");
	else
		_cprintf("No Shader Model 2.0 Support!\n");

	if( CheckShaderModel30(d3ddev) )
		_cprintf("Shader Model 3.0 Support!\n");
	else
		_cprintf("No Shader Model 3.0 Support!\n");

	
	d3ddev->SetRenderState( D3DRS_LIGHTING, FALSE );	 // Turn of Fixed Function Pipeline Lighting
	d3ddev->SetRenderState( D3DRS_ZENABLE, TRUE );		 // Use the depth buffer
	d3ddev->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); // Vertices defined in a counter-clockwise manner will be seen


	// Setup projection matrix (LH -> LeftHand)
	D3DXMATRIXA16 mProjection;
    D3DXMatrixPerspectiveFovLH( &mProjection, D3DXToRadian( 45.0f ), 1.0f, 0.1f, 100000.0f ); //Address of var, Fovy(field of view)[convert to radians], aspect, near, far
    d3ddev->SetTransform( D3DTS_PROJECTION, &mProjection );									  //Set the projection matrix

	//Create an instance of a level object
	leveltest = new LevelTest(d3ddev);		// New instance of a level object 
    init_graphics();						// Initialize the level and any other objects.
}


// this updates our scene
void Update(double dt)
{
	player_1->StartConrolling();	// Control Yarnball and camera Code 
	//sheet->Start(dt);				// Start animating
	//myText->updateAnimation(dt);	// Animate the text (as long as UseAnimation is set to true (Testing purposes)
	camera.Center( mainCharacter ); // Update the camera data to center on an object ( in this case yarnball object )
}

// Clears screen buffers
void render_start()
{
	d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);  // Clear the screen to black before rendering the objects (erase the screen to black before rendering)
	d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0); // Clear the depth buffer before we rendering the objects (Needed to know what object is in front of which and occlude that object)
	d3ddev->BeginScene(); //Tell Directx to draw to the window
}

// switch buffers
void render_end()
{
	d3ddev->EndScene(); //Tell Directx to draw to the window
    d3ddev->Present(NULL, NULL, NULL, NULL); // Switchs the backbuffer and front buffer.
}

// this is the function used to render a single frame
void render_scene(void)
{
	//Display Text (In Testing... [works just fine though!])

	// Set Text to follow yarnball (I'm going to expand on this for all entities so we can easily identify problem objects)
	myText->SetColor(1.0f, 1.0f, 0.0f);	// Set the color of the text : red, green, blue [0,1] floating point range
	myText->setText("Yarnball");		// Set text to be drawn
	myText->setPointSize(12);			// Set size of the text
	myText->setPosition( mainCharacter->ScreenPos.x, mainCharacter->ScreenPos.y-30); // Set the position of the text
	//myText->setPosition( mainCharacter ); // Doesn't work yet (renders but does not set the position correctly)
	myText->Render(d3ddev);				// Draw to the sreen

	// Set Text Name of Project
	myText->SetColor(1.0f, 0.0f, 0.0f);		// Set the color of the text
	myText->setText("String Game Project");	// Set text to be drawn
	myText->setPointSize(35);				// Set size of the text
	myText->setPosition( 0, 0);				// Set the position of the text
	myText->Render(d3ddev);					// Draw the text

	//Render LevelTest
	leveltest->Render();

	//sprite->setPosition(0, 0, 10); //For Testing Animation Uncomment if you want to see. (Don't expect much though)
	//sprite->Render(d3ddev);		 //

	//d3ddev->EndScene(); //Tell Directx to draw to the window

}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
	//Freeing the memory before closing the application
	FreeConsole();
	delete sheet;
	delete player_1;
	delete yarnTexture;
	delete aTexture;
	delete sprite;
	delete myText;
	delete leveltest;
	d3ddev->EvictManagedResources();
    d3ddev->Release();    // close and release the 3D device
    d3d->Release();		  // close and release Direct3D

	//Memory that wasn't allocated with the new operator automatically get deleted at the end of a function call or
	//the end of the application.
}


// this is the function that puts the 3D models into video RAM
void init_graphics(void)
{

	/////////////////
	/////////////////
	/////////////////

	// Create and load the yarnball texture
	// Check if loading the texture passed
	yarnTexture = new Texture(d3ddev);
	if( yarnTexture->LoadTexture("../../Art/blue.jpg", -1) )
		_cprintf("Texture Loaded!\n");
	else
		_cprintf("Texture Failed To Load!\n");

	/////////////////
	/////////////////
	/////////////////

	//Create and initialize the text

	myText = new Text();				// Allocate Memory Space for Text
	myText->init(d3ddev);				// Initialize the text

	myText->setPointSize( textsize );	// Set a pointsize
	myText->setText("String Game");		// Set text

	myText->UseAnimation(0);			//Use Text Animation : false or true [0 or 1]
	myText->FpsRate(1);					//Set Text Frame Rate (how many frames will be played every second; controls speed of animation)

	////////////////
	////////////////
	////////////////

	//Create and initialize/build the mainCharacter (yarnball)

	mainCharacter = new YarnBall(d3ddev, leveltest->GetWorldPhysics()); // Create the yarnball/mainCharacter instance and let it know where the world physics info is by passing leveltest's world physics address
	mainCharacter->build();												// Build the character (Load the character and load the equipment abilities and other stuff which are not completely implemented yet)
	mainCharacter->SetTexture (yarnTexture);							// Set the mainCharacter Texture/Image

	// Init, add the mainCharacter, the camera, and load the level (which level is loaded is hardcoded as a path ->see LevelTest.cpp in BuildLevel() function at the start)
	leveltest->AddMainCharacter(mainCharacter);		// Pass mainCharacter ptr
	leveltest->BuildLevel();						// Builds the level using the LevelTest class
	leveltest->SetLevelCamera(&camera);				// Pass camera ptr for the camera to take affect on the level


}

//Displat Game Statistics
void GameStats(double dt)
{
	static double mTime=0.0;
	static int cFPS=0;
	mTime+=dt;

	myText->setText("FPS:");
	myText->addText(cFPS);

	myText->SetColor(1.0f, 1.0f, 0.0f);	// Set the clor of the text
	myText->setPointSize(20);				// Set size of the text
	myText->setPosition( 0, 30);			// Set the position of the text

	if(mTime >= 1.0)
	{
		cFPS = nFPS;
		//cFPS = nFramesRendered;
		mTime=0.0;						// reset update time
		nFPS=0;
	}

	myText->Render(d3ddev);	// Draw the text


	/////
	/////

	myText->setText("nObjects:");
	myText->addText( leveltest->NumberOfObjectsInScene() );
	myText->setPosition( 0, 50);			// Set the position of the text
	myText->Render(d3ddev);

	/////
	/////

	myText->setText("nCullObjects:");
	myText->addText( leveltest->NumberOfCulledObjects());
	myText->setPosition( 0, 70);			// Set the position of the text
	myText->Render(d3ddev);

}